package com.ruoyi.activiti.service;

import com.github.pagehelper.Page;
import com.ruoyi.activiti.domain.LeaveBean;
import com.ruoyi.activiti.domain.dto.LeaveBeanDto;
import com.ruoyi.activiti.domain.vo.*;
import com.ruoyi.activiti.mapper.LeaveBeanMapper;
import com.ruoyi.activiti.utils.DateTimeUtils;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.*;
import org.activiti.engine.impl.util.IoUtil;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.DelegationState;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.Task;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class TaskTodoService {

    @Autowired
    private TaskService taskService;

    @Autowired
    private LeaveBeanMapper leaveBeanMapper;

    @Autowired
    private RuntimeService runtimeService;
    
    @Autowired
    private HistoryService historyService;
    
    @Autowired
    private RepositoryService repositoryService;

    /**
     * 任务列表
    * */
    public Page<TodoVo> todoTask(LeaveBeanDto leaveBeanDto) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        List<String> roleKeys = loginUser.getUser().getRoles().stream().map(SysRole::getRoleKey).collect(Collectors.toList());
        List<Task> tasks = new ArrayList<>();
        List<Task> list1 = taskService.createTaskQuery()
                .taskAssignee(loginUser.getUsername())
                .orderByTaskCreateTime().desc()
                .list();
        List<Task> list2 = taskService.createTaskQuery()
                .or()
                .taskCandidateUser(loginUser.getUsername())
                .taskCandidateGroupIn(roleKeys)
                .endOr()
                .orderByTaskCreateTime().desc()
                .list();
        tasks.addAll(list1);
        tasks.addAll(list2);


        // 获取总页数
        int total = 0;
        // 收集主键id
        List<String> procInsIds = tasks.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
        if(!procInsIds.isEmpty()){
            leaveBeanDto.setProcessInsIds(procInsIds);
            total = leaveBeanMapper.selectCountByInsId(leaveBeanDto);
        }
        // 手动分页
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        Page<TodoVo> list = new Page<>();
        if (pageNum != null && pageSize != null) {
            int maxRow = Math.min((pageNum - 1) * pageSize + pageSize, total);
            tasks = tasks.subList((pageNum - 1) * pageSize, maxRow);
            list.setTotal(total);
            list.setPageNum(pageNum);
            list.setPageSize(pageSize);
        }
        List<TodoVo> todoVos = new ArrayList<>();
        tasks.forEach(task -> {
            LeaveBean leaveBean = leaveBeanMapper.selectLeaveBeanByInsId(task.getProcessInstanceId());
            if(leaveBean!=null){
                TodoVo todoVo = new TodoVo();
                BeanUtils.copyProperties(leaveBean, todoVo);
                todoVo.setTodoName(task.getName());
                todoVo.setTodoUserName(task.getAssignee());
                todoVo.setTaskId(task.getId());
                todoVo.setSuspended(task.isSuspended());
                todoVos.add(todoVo);
            }
        });
       list.addAll(todoVos);
       return list;
    }

    /**
     * 处理任务
    * */
    public void handleTask(TodoHandleVo todoHandleVo) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Task task = taskService.createTaskQuery().taskId(todoHandleVo.getTaskId())
                .singleResult();
        if(task != null) {
            String  handleType = todoHandleVo.getHandleType();
            String taskDefinitionKey = task.getTaskDefinitionKey();
            HashMap<String, Object> hm = new HashMap<>();
            // 先判断流程是否被挂起
            boolean suspended = task.isSuspended();
            if(suspended && !"6".equals(handleType)){
                // 如果传过来的type是不是6解挂并且流程还是挂起状态则抛出异常
                throw new RuntimeException("流程已挂起,请先解挂流程");
            }
            if(!StringUtils.isBlank(todoHandleVo.getComment())) {
                switch (handleType) {
                    case "1":
                        taskService.addComment(task.getId(), task.getProcessInstanceId(), "【同意】"+todoHandleVo.getComment());
                        break;
                    case "3":
                        taskService.addComment(task.getId(), task.getProcessInstanceId(), "【转交】"+todoHandleVo.getComment());
                        break;
                    case "4":
                        taskService.addComment(task.getId(), task.getProcessInstanceId(), "【终止】"+todoHandleVo.getComment());
                        break;
                }
            }
            if(taskDefinitionKey.equals(LeaveNodeName.Bu_Men_Jing_Li.getValue())){
                LeaveBean leaveBean = leaveBeanMapper.selectLeaveBeanByInsId(task.getProcessInstanceId());
                hm.put("day", leaveBean.getLeaveCount());
            } else if(taskDefinitionKey.equals(LeaveNodeName.Mi_Shu_Zhang.getValue()) ||
            taskDefinitionKey.equals(LeaveNodeName.Fu_Mi_Shu_Zhang.getValue())) {
                if("1".equals(handleType)) {
                    // 通过
                    hm.put("approve", 0);
                } else if("2".equals(handleType)) {
                    // 拒绝
                    hm.put("approve", 1);
                }
            } else if(taskDefinitionKey.equals(LeaveNodeName.Dong_Shi_Zhang.getValue())||
            taskDefinitionKey.equals(LeaveNodeName.Fu_Dong_Shi_Zhang.getValue())) {
                if("2".equals(handleType)) {
                    // 不是排它网关并且拒绝的直接终止流程与传类型4处理逻辑一样
                    // 执行此方法后未审批的任务 act_ru_task 会被删除，流程历史 act_hi_taskinst 不会被删除，并且流程历史的状态为finished完成
                    runtimeService.deleteProcessInstance(task.getProcessInstanceId(), todoHandleVo.getComment());
                    return;
                }
            }
            // 转交任务
            if("3".equals(handleType)) {
                // 判断assignee是否为空，为空的话先拾取 在转交
                taskService.resolveTask(task.getId());
                if(StringUtils.isBlank(task.getAssignee())) {
                    taskService.claim(task.getId(), loginUser.getUsername());
                }
                taskService.delegateTask(todoHandleVo.getTaskId(), todoHandleVo.getTransUser());
                return;
            }
            // 撤销任务
            if("4".equals(handleType)) {
                // 执行此方法后未审批的任务 act_ru_task 会被删除，流程历史 act_hi_taskinst 不会被删除，并且流程历史的状态为finished完成
                runtimeService.deleteProcessInstance(task.getProcessInstanceId(), todoHandleVo.getComment());
                return;
            }
            // 挂起
            if("5".equals(handleType)) {
                // 当流程实例被挂起时，无法通过下一个节点对应的任务id来继续这个流程实例。
                // 通过挂起某一特定的流程实例，可以终止当前的流程实例，而不影响到该流程定义的其他流程实例。
                // 激活之后可以继续该流程实例，不会对后续任务造成影响。
                // 直观变化：act_ru_task 的 SUSPENSION_STATE_ 为 2
                runtimeService.suspendProcessInstanceById(task.getProcessInstanceId());
                return;
            }
            // 解挂
            if("6".equals(handleType)) {
                runtimeService.activateProcessInstanceById(task.getProcessInstanceId());
                return;
            }

            // 被委派人处理一定要先resolveTask 不然报错
            // p.s. 被委托的流程需要先 resolved 这个任务再提交。
            // 所以在 complete 之前需要先 resolved
            // resolveTask() 要在 claim() 之前，不然 act_hi_taskinst 表的 assignee 字段会为 null
            log.info("任务委派状态:{}",task.getDelegationState() == DelegationState.PENDING);
            if(task.getDelegationState() == DelegationState.PENDING) {
                taskService.resolveTask(task.getId());
            }
            String assignee = task.getAssignee();
            if(StringUtils.isBlank(assignee)) {
                // 处理人为空先拾取任务
                taskService.claim(task.getId(), loginUser.getUsername());
            }
            // 处理任务
            taskService.complete(task.getId(),hm);
        } else {
          throw new RuntimeException("待办任务为空");
        }
    }
    
    /**
     * 查看流程图和高亮节点
    * */
    public ProcessHighlightEntity processViewer(String procDefId,String processInstanceId)  {
        ProcessHighlightEntity highlight = new ProcessHighlightEntity();
        // 获取一条流程实例历史
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        // 根据流程定义 key 获取 BMPN
        BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
        // 获取流程
        Process process = bpmnModel.getProcesses().get(0);
        // 获取所有流程 FlowElement 的信息，就是所有bpmn的节点
        Collection<FlowElement> flowElements = process.getFlowElements();

        /**
         * 这个 map 中的 key 就是 开始节点和结束节点的编号拼起来的字符串，
         * value 就是开始节点和结束节点的连线，到时候我们根据开始节点和结束节点
         * 就可以获取到需要高亮的连线
         */
        Map<String,String> map = new HashMap<String,String>();
        for (FlowElement flowElement : flowElements) {
            // 判断是否是线条
            if(flowElement instanceof SequenceFlow){
                SequenceFlow sequenceFlow = (SequenceFlow)flowElement;
                String ref = sequenceFlow.getSourceRef();
                String targetRef = sequenceFlow.getTargetRef();
                /**
                 * 保存开始节点和结束节点，与它们之间连线的对应关系
                 * key: 开始节点 编号 + 结束节点 编号
                 * value: 连线编号
                 */
                map.put(ref+targetRef,sequenceFlow.getId());
            }
        }

        /**
         * 获取已经完成的全部流程历史节点
         */
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .list();
        /**
         * 将各个历史节的开始节点和结束几点的编号两两对应起来，
         * 就可以从上面的 map 中获取到需要高亮的连线
         */
        Set<String> keyList = new HashSet<String>();
        for (HistoricActivityInstance i : list) {
            for (HistoricActivityInstance j : list) {
                if(i != j){
                    keyList.add(i.getActivityId()+j.getActivityId());
                }
            }
        }

        // 获取高亮连线 id
        Set<String> highLine = new HashSet<String>();
        // 根据已经完成的（开始节点编号+结束节点编号）组成的 key 获取需要高亮的连线编号
        keyList.forEach(s -> highLine.add(map.get(s)));


        // 获取已经完成的节点
        List<HistoricActivityInstance> listFinished = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .finished()
                .list();

        // 已经完成的节点高亮
        Set<String> highPoint = new HashSet<>();
        // 保存已经完成的流程节点编号
        listFinished.forEach(s -> highPoint.add(s.getActivityId()));

        // 获取代办节点
        List<HistoricActivityInstance> listUnFinished = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .unfinished()
                .list();

        // 代办的节点高亮
        Set<String> waitingToDo = new HashSet<>();
        // 保存需要代办的节点编号
        listUnFinished.forEach(s -> waitingToDo.add(s.getActivityId()));

        // 高亮线
        highlight.setHighlightedFlowIds(highLine);
        // 进行中的节点
        highlight.setActiveActivityIds(waitingToDo);
        // 已经完成的节点
        highlight.setExecutedActivityIds(highPoint);

        try {
            ProcessDefinition processDefinition = getProcessDefinition(procDefId, processInstanceId);
            highlight.setModelName(processDefinition.getName());
            InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                    processDefinition.getResourceName());
            byte[] bytes = IoUtil.readInputStream(resourceAsStream, processDefinition.getResourceName());
            String bpmnxML = new String(bytes, "utf-8");
            highlight.setBpmnXml(bpmnxML);
        } catch (Exception e) {
            throw new RuntimeException("流程文件转换异常");
        }

        return highlight;
    }

    public ProcessDefinition getProcessDefinition(String procDefId, String instanceId) {
        if (StringUtils.isBlank(procDefId)) {
            if (StringUtils.isBlank(instanceId)) {
                throw new RuntimeException("流程实例id，流程定义id 两者不能都为空");
            }
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(instanceId)
                    .singleResult();
            if (processInstance == null) {
                HistoricProcessInstance histProcInst = historyService.createHistoricProcessInstanceQuery()
                        .processInstanceId(instanceId)
                        .singleResult();
                if (histProcInst == null) {
                    throw new RuntimeException("查询失败，请检查传入的 instanceId 是否正确");
                }
                procDefId = histProcInst.getProcessDefinitionId();
            } else {
                procDefId = processInstance.getProcessDefinitionId();
            }
        }

        try {
            return repositoryService.getProcessDefinition(procDefId);
        } catch (Exception e) {
            throw new RuntimeException("该流程属于之前流程，已删除");
        }
    }

    public Page<TodoDoneVo> doneLeave() {
        // 手动分页
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        Page<TodoDoneVo> list = new Page<>();

        List<TodoDoneVo> results = new ArrayList<>();
        List<HistoricTaskInstance> hisList = findDoneTasks(SecurityUtils.getUsername(), "levave_1");
        // 根据流程的业务ID查询实体并关联
        for (HistoricTaskInstance instance : hisList) {
            String processInstanceId = instance.getProcessInstanceId();
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            String businessKey = processInstance.getBusinessKey();
            // 根据当时存储的请假申请表主键查询
            LeaveBean leaveBean = leaveBeanMapper.selectLeaveBeanById(Long.valueOf(businessKey));
            if(leaveBean == null) {
                continue;
            }
            // 对象转化拷贝
            TodoDoneVo todoDoneVo = new TodoDoneVo();
            BeanUtils.copyProperties(leaveBean, todoDoneVo);

            todoDoneVo.setTaskStartTime(instance.getStartTime());
            todoDoneVo.setTaskEndTime(instance.getEndTime());
            String duration = DateTimeUtils.millConverterStr(instance.getDurationInMillis());
            todoDoneVo.setDuration(duration);
            todoDoneVo.setTodoName(instance.getName());
            todoDoneVo.setTodoUserName(instance.getAssignee());
            results.add(todoDoneVo);
        }

        List<TodoDoneVo> tempList;
        if (pageNum != null && pageSize != null) {
            int maxRow = Math.min((pageNum - 1) * pageSize + pageSize, results.size());
            tempList = results.subList((pageNum - 1) * pageSize, maxRow);
            list.setTotal(results.size());
            list.setPageNum(pageNum);
            list.setPageSize(pageSize);
        } else {
            tempList = results;
        }
        list.addAll(tempList);

        return list;
    }

    public List<HistoricTaskInstance> findDoneTasks(String userId, String key) {
        List<HistoricTaskInstance> list = historyService
                .createHistoricTaskInstanceQuery()
                .processDefinitionKey(key)
                .taskAssignee(userId)
                .finished()
                .orderByHistoricTaskInstanceEndTime()
                .desc()
                .list();
        return list;
    }

    public List<TodoDoneVo> selectHistoryList(String processInstanceId) {
//        PageDomain pageDomain = TableSupport.buildPageRequest();
//        Integer pageNum = pageDomain.getPageNum();
//        Integer pageSize = pageDomain.getPageSize();
        List<TodoDoneVo> activityList = new ArrayList<>();
        TodoDoneVo todoDoneVo1 = new TodoDoneVo();
        // 拼接申请开始节点
        LeaveBean leaveBean = leaveBeanMapper.selectLeaveBeanByInsId(processInstanceId);
        todoDoneVo1.setTodoName("开始");
        todoDoneVo1.setTodoUserName(leaveBean.getCreateBy());
        todoDoneVo1.setTaskStartTime(leaveBean.getCreateTime());
        todoDoneVo1.setComment(leaveBean.getLeaveReason());
        activityList.add(todoDoneVo1);
        HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery();
        List<HistoricActivityInstance> list = query.processInstanceId(processInstanceId)
                .activityType("userTask")
                .finished()
                .orderByHistoricActivityInstanceStartTime()
                .asc()
                .list();
//                .listPage((pageNum - 1) * pageSize, pageNum * pageSize);
        list.forEach(instance -> {
            TodoDoneVo todoDoneVo = new TodoDoneVo();
            todoDoneVo.setTaskStartTime(instance.getStartTime());
            todoDoneVo.setTaskEndTime(instance.getEndTime());
            todoDoneVo.setTodoName(instance.getActivityName());
            String taskId = instance.getTaskId();
            List<Comment> comment = taskService.getTaskComments(taskId, "comment");
            if (!CollectionUtils.isEmpty(comment)) {
                todoDoneVo.setComment(comment.get(0).getFullMessage());
            }
            todoDoneVo.setTodoUserName(instance.getAssignee());
            // 转化毫秒值
            Long durationInMillis = instance.getDurationInMillis();
            if(durationInMillis  != null) {
                String millConverterStr = DateTimeUtils.millConverterStr(durationInMillis);
                todoDoneVo.setDuration(millConverterStr);
            }
            activityList.add(todoDoneVo);
        });
        // 拼接结束节点
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        if(processInstance == null) {
            // 流程已结束
            TodoDoneVo todoDoneVo = new TodoDoneVo();
            todoDoneVo.setTodoName("结束");
            activityList.add(todoDoneVo);
        } else {
            // 流程没有结束查询task表中的下个节点办理人拼接上去
            Task task = taskService.createTaskQuery()
                    .processInstanceId(processInstanceId)
                    .singleResult();
            String assignee = task.getAssignee();
            TodoDoneVo todoDoneVo = new TodoDoneVo();
            todoDoneVo.setTodoName(task.getName());
            if(StringUtils.isNotBlank(assignee)) {
                todoDoneVo.setTodoUserName(assignee);
            } else {
                // 查询候选用户或候选组
                String candidates = taskService.getIdentityLinksForTask(task.getId())
                        .stream()
                        .map(item -> {
                            // 判断候选人为空取候选组
                            if(!StringUtils.isBlank(item.getUserId())) {
                                return item.getUserId();
                            } else {
                                return item.getGroupId();
                            }
                        })
                        .collect(Collectors.joining(","));
                todoDoneVo.setTodoUserName(candidates);
                todoDoneVo.setTaskStartTime(task.getCreateTime());
            }
            activityList.add(todoDoneVo);
        }
        return activityList;
    }
}
